package cz.muni.fi.pa165.machineryrental.weblayer.controllers;

import cz.muni.fi.pa165.machineryrental.dto.CustomerDTO;
import cz.muni.fi.pa165.machineryrental.dto.MachineDTO;
import cz.muni.fi.pa165.machineryrental.dto.RentalDTO;
import cz.muni.fi.pa165.machineryrental.weblayer.utils.Utils;
import cz.muni.fi.pa165.machineryrental.service.CustomerService;
import cz.muni.fi.pa165.machineryrental.service.MachineService;
import cz.muni.fi.pa165.machineryrental.service.RentalService;
import cz.muni.fi.pa165.machineryrental.weblayer.custommodels.CustomerMachineSearch;
import cz.muni.fi.pa165.machineryrental.weblayer.editors.CustomerEditor;
import cz.muni.fi.pa165.machineryrental.weblayer.editors.MachineEditor;
import cz.muni.fi.pa165.machineryrental.weblayer.validator.RentalValidator;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import javax.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.propertyeditors.CustomDateEditor;
import org.springframework.security.access.annotation.Secured;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;

/**
 *
 * @author jostri
 */
@Controller
public class RentalController {

    @Autowired
    private RentalService rentalService;
    @Autowired
    private CustomerService customerService;
    @Autowired
    private MachineService machineService;
    @Autowired
    private RentalValidator rentalValidator;

    @InitBinder
    public void initBinder(WebDataBinder webDataBinder) {
        SimpleDateFormat sf = Utils.getCurrentSimpleDateFormat();
        webDataBinder.registerCustomEditor(Date.class, new CustomDateEditor(sf, true));
        webDataBinder.setValidator(this.rentalValidator);

        webDataBinder.registerCustomEditor(MachineDTO.class, new MachineEditor(this.machineService));
        webDataBinder.registerCustomEditor(CustomerDTO.class, new CustomerEditor(this.customerService));
    }

    @RequestMapping("/rental/add")
    public ModelAndView add() {

        ModelAndView mav = this.getModelAndViewForAdd("/rental/add.jsp");
        mav.addObject("rental", new RentalDTO());

        return mav;
    }

    @RequestMapping(value = "/rental/add", method = RequestMethod.POST)
    public ModelAndView add(@Valid @ModelAttribute("rental") RentalDTO rental, BindingResult result) {

        if (result.hasErrors()) {
            return this.getModelAndViewForAdd("/rental/add.jsp");
        }

        if (rental.getDateOfReturn() != null) {
            long diff = rental.getDateOfReturn().getTime() - rental.getDateOfRent().getTime();
            rental.setDuration((int) TimeUnit.DAYS.convert(diff, TimeUnit.MILLISECONDS) + 1);
        }

        this.rentalService.create(rental);
        return new ModelAndView("redirect:/rental/detail/" + rental.getId());
    }

    @RequestMapping(value = "/rental/edit/{id}", method = RequestMethod.GET)
    public ModelAndView edit(@PathVariable long id) {

        RentalDTO rent = this.rentalService.getById(id);
        if (rent == null) {
            ModelAndView mav = this.getModelAndViewForEdit("/rental/edit.jsp", rent);
            mav.addObject("rent", null);
            return mav;
        }

        ModelAndView mav = this.getModelAndViewForEdit("/rental/edit.jsp", rent);
        mav.addObject("rent", rent);

        return mav;
    }

    @RequestMapping(value = "/rental/edit", method = RequestMethod.POST)
    public ModelAndView edit(@Valid @ModelAttribute("rent") RentalDTO rental, BindingResult result) {

        if (result.hasErrors()) {
            ModelAndView mav = this.getModelAndViewForAdd("/rental/edit.jsp");
            mav.addObject("rent", rental);
            return mav;
        }

        if (rental.getDateOfReturn() != null) {
            long diff = rental.getDateOfReturn().getTime() - rental.getDateOfRent().getTime();
            rental.setDuration((int) TimeUnit.DAYS.convert(diff, TimeUnit.MILLISECONDS) + 1);
        }

        this.rentalService.update(rental);
        return new ModelAndView("redirect:/rental/detail/" + rental.getId());
    }

    @RequestMapping("/rental/index")
    public ModelAndView index() {

        ModelAndView modelAndView = new ModelAndView("/rental/index.jsp", "rentals", this.rentalService.findAll());
        modelAndView.addObject("searchModel", new CustomerMachineSearch());
        modelAndView.addObject("customersMap", this.getCustomersMap());
        modelAndView.addObject("machinesMap", this.getMachinesMap());

        return modelAndView;
    }

    @RequestMapping("/rental/index/{customerId}")
    public ModelAndView index(@PathVariable long customerId) {

        CustomerDTO customer = this.customerService.getById(customerId);

        Map<String, Object> model = new LinkedHashMap<String, Object>();
        if (customer == null) {
            model.put("rentals", this.rentalService.findAll());
        } else {
            model.put("rentals", this.rentalService.findByCustomer(customer));
        }
        model.put("customers", this.getCustomersMap());

        ModelAndView modelAndView = new ModelAndView("/rental/index.jsp", "model", model);
        modelAndView.addObject("searchModel", new CustomerMachineSearch());

        return modelAndView;
    }

    @RequestMapping(value = "/rental/search", method = RequestMethod.POST)
    public ModelAndView index(@ModelAttribute("searchModel") CustomerMachineSearch search, BindingResult bindingResult) {

        List<RentalDTO> rentals;
        if (search == null || (search.getCustomer() == null && search.getMachine() == null)) {
            rentals = this.rentalService.findAll();
        } else {
            if (search.getCustomer() != null && search.getMachine() != null) {
                rentals = this.rentalService.findByCustomerMachine(search.getCustomer(), search.getMachine());
            } else {
                if (search.getCustomer() != null) {
                    rentals = this.rentalService.findByCustomer(search.getCustomer());
                } else {
                    rentals = this.rentalService.findByMachine(search.getMachine());
                }
            }
        }

        ModelAndView modelAndView = new ModelAndView("/rental/index.jsp", "rentals", rentals);
        modelAndView.addObject("searchModel", new CustomerMachineSearch());
        modelAndView.addObject("customersMap", this.getCustomersMap());
        modelAndView.addObject("machinesMap", this.getMachinesMap());

        return modelAndView;
    }

    @RequestMapping("/rental/detail/{id}")
    public ModelAndView detail(@PathVariable long id) {
        return new ModelAndView("/rental/detail.jsp", "rental", rentalService.getById(id));
    }

    private Map<String, String> getCustomersMapAdd() {
        List<CustomerDTO> customers = this.customerService.findAll();
        Map<String, String> formCustomers = new LinkedHashMap<String, String>();

        for (CustomerDTO c : customers) {
            formCustomers.put(c.getId() + "", BuildCustomer(c).toString());
        }

        return formCustomers;
    }

    private Map<String, String> getMachinesMapAdd() {
        List<MachineDTO> machines = this.machineService.findAll();
        Map<String, String> formMachines = new LinkedHashMap<String, String>();

        for (MachineDTO m : machines) {
            formMachines.put(m.getId() + "", BuildMachine(m).toString());
        }

        return formMachines;
    }

    private Map<String, String> getCustomersMap() {
        List<CustomerDTO> customers = this.customerService.findAll();
        Map<String, String> formCustomers = new LinkedHashMap<String, String>();

        formCustomers.put("", "");

        for (CustomerDTO c : customers) {
            formCustomers.put(c.getId() + "", BuildCustomer(c).toString());
        }

        return formCustomers;
    }

    private Map<String, String> getMachinesMap() {
        List<MachineDTO> machines = this.machineService.findAll();
        Map<String, String> formMachines = new LinkedHashMap<String, String>();

        formMachines.put("", "");

        for (MachineDTO m : machines) {
            formMachines.put(m.getId() + "", BuildMachine(m).toString());
        }

        return formMachines;
    }

    private ModelAndView getModelAndViewForAdd(String path) {
        Map<String, String> formCustomers = this.getCustomersMapAdd();
        Map<String, String> formMachines = this.getMachinesMapAdd();

        ModelAndView mav = new ModelAndView(path);
        mav.addObject("customersMap", formCustomers);
        mav.addObject("machinesMap", formMachines);

        return mav;
    }

    private Map<String, String> getCustomersMapForEdit(RentalDTO rent) {
        List<CustomerDTO> customers = customerService.findAll();
        Map<String, String> formCustomers = new LinkedHashMap<String, String>();
        formCustomers.put(rent.getCustomer().getId() + "", BuildCustomer(rent.getCustomer()).toString());

        for (CustomerDTO c : customers) {
            if (c.getId() != rent.getCustomer().getId()) {
                formCustomers.put(c.getId() + "", BuildCustomer(c).toString());
            }
        }

        return formCustomers;
    }

    private ModelAndView getModelAndViewForEdit(String path, RentalDTO rent) {
        Map<String, String> formCustomers = this.getCustomersMapForEdit(rent);

        List<MachineDTO> machines = machineService.findAll();
        Map<String, String> formMachines = new LinkedHashMap<String, String>();
        formMachines.put(rent.getMachine().getId() + "", BuildMachine(rent.getMachine()).toString());

        for (MachineDTO m : machines) {
            if (m.getId() != rent.getMachine().getId()) {
                formMachines.put(m.getId() + "", BuildMachine(m).toString());
            }
        }

        ModelAndView mav = new ModelAndView(path);
        mav.addObject("customersMap", formCustomers);
        mav.addObject("machinesMap", formMachines);

        return mav;
    }

    private StringBuilder BuildCustomer(CustomerDTO c) {
        StringBuilder temp = new StringBuilder(c.getLastName());
        temp.append(" ");
        temp.append(c.getFirstName());
        temp.append(" (");
        temp.append(c.getEmail());
        temp.append(")");
        return temp;
    }

    private StringBuilder BuildMachine(MachineDTO m) {
        StringBuilder temp = new StringBuilder(m.getCode());
        temp.append(" - ");
        temp.append(m.getBrand());
        temp.append(" ");
        temp.append(m.getModel());
        return temp;
    }

    @Secured("ROLE_ADMIN")
    @RequestMapping("/rental/remove/{id}")
    public ModelAndView remove(@PathVariable long id) {

        RentalDTO rental = this.rentalService.getById(id);
        rentalService.remove(rental);
        return new ModelAndView("redirect:/rental/index");
    }
}
